Skip to content

fix(server): reject Git-confirmed bare-root projects - #8646

Closed
walid-baharwal wants to merge 6 commits into
pingdotgg:mainfrom
walid-baharwal:fix/bare-root-worktree-project
Closed

walid-baharwal wants to merge 6 commits into
pingdotgg:mainfrom
walid-baharwal:fix/bare-root-worktree-project

Conversation

@walid-baharwal

@walid-baharwal walid-baharwal commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Human hold

This is a narrower project-add policy proposal for #8164, not a complete fix for the original report. Keep it unmerged until a maintainer approves rejecting Git-confirmed bare roots. Already-converted nonbare roots remain accepted. No initialization behavior changes are included.

Change

A root whose .git file points to an embedded bare repository has no checkout of its own. Project add now rejects that root with an explanation and directs the user to an individual worktree. The check applies to CLI add and normalized project.create commands.

The original index heuristic rejected a valid unborn separate-git-dir checkout with a linked worktree and accepted a bare repository after git read-tree --empty created an index. The correction asks Git for its current bare status instead. It makes one bounded query only for in-root gitdir pointers; ordinary repositories, directories without a pointer, and external linked-worktree pointers keep the no-process fast path. Query errors remain accepted rather than guessing.

The shared pointer parser preserves native separators for the eligibility filter; devHome keeps its original backslash normalization. The final query gives Git the .git file itself, so Git—not the filter—resolves pointer syntax and trailing spaces. GIT_WORK_TREE is removed for this probe, and the explicit gitfile prevents an inherited GIT_DIR from redirecting the query.

Before and after

Real Git, production handlers and disposable SQLite fixtures on Linux. Original guard tested against main 931d41f9. Candidate includes main 2fb99a7a.

Layout Original guard Candidate
Bare pointer root with linked worktree Rejected Rejected
Bare pointer root with an index Incorrectly accepted Rejected
Bare pointer root before its first worktree Accepted Rejected
Valid unborn separate-git-dir checkout with linked worktree Incorrectly rejected Accepted
Root already converted to nonbare by git init Rejected by heuristic Accepted deliberately
Valid POSIX store\name gitdir beside a different bare store/name Initial query candidate read the wrong path Correct gitdir queried

The expanded WorkspacePaths tests fail in four cases against the old guard and pass with the correction. The POSIX backslash regression failed before the raw-parser repair. Three further review regressions failed on 2b1d4d88 and pass with the gitfile-query correction: malformed pointer headers beside an unrelated bare repo, a valid trailing-space gitdir beside a bare trimmed-name sibling, and inherited Git environment overrides. Controls cover both GIT_WORK_TREE and GIT_DIR. CLI/SQLite tests verify that rejected roots create no project record and that valid or already-converted roots remain addable. The same fixtures exercise production command normalization.

cd apps/server
vp test run src/workspace/WorkspacePaths.test.ts src/bin.test.ts \
  src/orchestration/Normalizer.test.ts src/cli/project.test.ts --maxWorkers 2 \
  -t 'WorkspacePathsLive|current Git bare status|canonicalizeClientCommandTimestamps|maps declared server failures|preserves unexpected server failures'
../../node_modules/.bin/tsgo --noEmit
cd ../../packages/shared
vp test run src/devHome.test.ts src/git.test.ts --maxWorkers 2
../../node_modules/.bin/tsgo --noEmit

56 focused tests pass, including the Windows-style devHome pointer control. Scoped types, lint, formatting and whitespace checks pass. This is nonvisual server validation; no native macOS client add/start evidence is claimed. At e80cb32c, all executed CI jobs, Macroscope Correctness/Approvability and Cursor Bugbot pass. All five review threads are resolved; there are no active change requests. Preview jobs and CodeRabbit's disabled automatic review are skipped. The watcher completed. The human policy hold and missing native add/start proof remain; this is not a complete fix for the original issue.

Limits

  • Git can describe the current repository, not recover the intent of every root already converted to nonbare. A valid unborn checkout and a converted root produced matching bare/inside flags, local config and HEAD in the audit. No replacement heuristic is introduced.
  • The exact report's bare-clone commands left core.bare=true in the Linux fixture. T3's existing initRepository changed it to false, after which the detector accepted the parent and status listed sibling worktrees as untracked. This PR does not change that initialization path or repair existing projects.
  • Startup auto-bootstrap dispatches directly and still bypasses command normalization. HTTP dispatch still maps normalization errors to its existing generic invalid_command response.
  • This does not decide policy for external gitdir pointers, roots that are themselves bare gitdirs, or already-converted layouts. The original issue stays open.

Original implementation by Walid Baharwal is preserved in 48fc1cd9; current main was merged without rewriting the author's history.

Correction and verification: GPT 6 Astra via Codex in T3 Code.


Note

Medium Risk
Changes project-creation validation and shells out to Git for some workspace roots; if Git is unavailable, bare layouts may still be accepted by design.

Overview
Project add now rejects workspace roots whose in-root .git file points at an embedded bare repository, with a clear error directing users to add an individual worktree instead. The check runs on project add and on normalized project.create commands only; other callers still accept bare pointer roots after normalization.

Detection replaces index/heuristic guards with a bounded git rev-parse --is-bare-repository query (via ensureNotBareRepositoryLayout) for eligible in-root gitdir pointers, while keeping a no-Git fast path for ordinary repos and external pointers. Git failures or spawn errors accept the root rather than block add.

Shared parseGitDirPointer centralizes .git file parsing; devHome uses it for linked-worktree detection. Broad unit and CLI integration tests cover bare, separate-git-dir, worktree, converted, and edge-case layouts.

Reviewed by Cursor Bugbot for commit e80cb32. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Reject bare-root Git repositories in project-add and command normalization

  • Adds ensureNotBareRepositoryLayout to the WorkspacePaths service, which probes when .git is a gitfile pointing strictly inside the normalized root and asks Git --is-bare-repository; nonzero or failed Git results are treated as not bare.
  • CLI projectAddMutation in project.ts and orchestration normalizeDispatchCommand in Normalizer.ts now call the check after normalizeWorkspaceRoot, returning a new WorkspaceRootBareRepositoryLayoutError (mapped to OrchestrationDispatchCommandError in the normalizer).
  • Extracts a shared parseGitDirPointer utility in git.ts for gitdir: lines from .git files; devHome.pointsAtLinkedWorktree now uses it.
  • Behavioral Change: roots classified as in-root bare repositories are now rejected at add/normalize time instead of being accepted as projects; Git spawn failures or non-true results do not trigger rejection.

Macroscope summarized e80cb32.

@coderabbitai

coderabbitai Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 77613745-32fe-48fb-98b3-ac557baac157

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 29, 2026
Comment thread apps/server/src/workspace/WorkspacePaths.ts Outdated
Comment thread apps/server/src/cli/project.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9ecd6caa04a03da511266a2370c6d113581566af. Configure here.

Comment thread apps/server/src/workspace/WorkspacePaths.ts Outdated
A worktree-only layout keeps a bare repository inside the root (commonly
`<root>/.bare`) and checks every branch out as a sibling directory, so the
root holds no working tree of its own. `git rev-parse --is-inside-work-tree`
still answers true there, so the root was accepted as an ordinary repository
and became the parent of every worktree: status reported each worktree as
untracked, the diff view was scoped across all of them, and a thread's cwd
spanned every checkout at once.

Recognize the layout where a project is added and explain that a worktree
directory inside it is what to add. Detection reads the `.git` file: a
directory `.git` is an ordinary repository, and a `.git` file pointing outside
the root is a linked worktree or a submodule. What remains is shared with
`git init --separate-git-dir`, and git records nothing that separates the two —
it writes no `core.worktree` for either — so two structural signals are
required together: the git directory hosts linked worktrees, and it never
staged anything of its own. A working tree that has committed has an index; a
bare repository does not.

Anything ambiguous is accepted, since wrongly refusing a valid root is worse
than the misscoping this prevents. Containment is tested with a relative path
so a root at the filesystem boundary is compared correctly.

Fixes pingdotgg#8164
@macroscopeapp

macroscopeapp Bot commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at e80cb32

Macroscope's review found this PR approvable — This is a focused server validation fix that rejects only Git-confirmed bare repository roots during project addition while preserving ordinary repositories, worktrees, and converted roots. The implementation is localized and backed by broad edge-case and integration coverage.

You can add or adjust custom eligibility rules. Learn more.

@walid-baharwal
walid-baharwal force-pushed the fix/bare-root-worktree-project branch from 9ecd6ca to 48fc1cd Compare August 29, 2026 12:50
@juliusmarminge

Copy link
Copy Markdown
Member

Current-main audit for #8164, tested on 0dd5c64bc on September 4, 2026.

The exact bare-clone, .git pointer, and sibling-worktree commands leave core.bare=true in my disposable Linux fixture. The production detector returns null for the parent and detects the child worktree correctly. Calling the production initRepository on that parent changes core.bare to false. The detector then accepts the parent, and Git status reports the sibling worktrees as untracked. That reproduces the problematic layout, with an initialization step beyond the report's shell commands.

This PR still needs human review. The missing-index heuristic has outstanding counterexamples in the existing reviews, including a legitimate empty separate-git-dir repository. I have not verified the current PR head against those cases or the actual add/start UI path, so this comment is not a merge-readiness recommendation. Please keep the valid-layout controls when revising the classification rule.

GPT 6 Astra via Codex in T3 Code.

@juliusmarminge juliusmarminge changed the title fix(server): reject a bare repository root when adding a project fix(server): reject Git-confirmed bare-root projects Sep 5, 2026
Comment thread packages/shared/src/git.ts
Comment thread apps/server/src/workspace/WorkspacePaths.ts
@juliusmarminge

Copy link
Copy Markdown
Member

Thanks for the PR. We're not taking changes to the orchestration and provider layers right now: that part of the server is being rewritten for V2, and merging into the current code would either conflict with or be thrown away by that work.

Closing for now. If this is still an issue once V2 lands, please reopen (or open a fresh PR against the new code) and we'll take a proper look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants